import { CreepAbilityHelper } from "./CreepAbilityHelper"
import { CreepBoostSetup } from "./CreepBoostSetup"
import { BODYPART_ABILITY_Map } from "./setups"

export class BodyUtils
{


    /**
     * 分类身体部件
     * @param bodyParts 身体部件
     */
    static classifyBodyParts(bodyParts: BodyPartConstant[]): PartsClassify {
        const partsClassifyObj: PartsClassify = {}
        bodyParts.forEach((type) => {
            const recordOnType = partsClassifyObj[type]
            if (!recordOnType) partsClassifyObj[type] = 1
            else (partsClassifyObj[type] as any)++
        })
        return partsClassifyObj
    }


    /**
     * 将特定格式的身体部件展开，如
     * 将 [[MOVE,2],[WORK,1]] 展开成 [MOVE,MOVE,WORK] 的形式
     * 将 [[CARRY,MOVE],2] 展开成 [CARRY,MOVE,CARRY,MOVE] 的形式
     */
    static flatBodyParts(partsArray: BodyItem): BodyPartConstant[] {
        if (typeof partsArray == 'string') {
            return partsArray
        }
        // 如果数组最后一个元素是数字
        const last = partsArray[partsArray.length - 1]
        let a: any[] = []
        if (typeof last == 'number') {
            for (let i = 0; i < last; i++) {
                a = a.concat(this.flatBodyParts(partsArray[0] as BodyItem))
            }
        } else {
            partsArray.forEach((r) => (a = a.concat(this.flatBodyParts(r as BodyItem))))
        }
        return a
    }
    
    static calculateEnergyCost(partsArray: BodyItem)
    {
        const bodys = this.flatBodyParts(partsArray);
        return this.calculateSpawnEnergy(bodys);
    }
    /**
     * 
     * @param existBody 
     * @param boostlevel 
     * @returns 求满速度 body需要的 move数量
     */
    static getMoveBodypartNumber(existBody:Record<BodyPartConstant, number>,boostlevel:BoostLevel)
    {
        let sum=0;
        for(let k in existBody)
        {
            sum+=existBody[k as BodyPartConstant];
        }
        return this.getMoveCount(sum,boostlevel);
    }
    static getMoveCount(otherBodycount:number,boostlevel:BoostLevel)
    {
        const minerial = boostlevel?CreepBoostSetup.BoostConstant(MOVE,"fatigue",boostlevel):undefined;
        return CreepAbilityHelper.GetMinBodyForMove(otherBodycount,minerial)
    }
    static flatBodyPartMap(partsArray: BodyItem):  Record<BodyPartConstant,number>  {
        if (typeof partsArray == 'string') {
            return partsArray
        }
        // 如果数组最后一个元素是数字
        const last = partsArray[partsArray.length - 1]
        let a:Record<BodyPartConstant,number> = {} as any;
        if (typeof last == 'number') {
           // 前面要么是字符串，要么是数组
            if(typeof partsArray[0] == "string")
            {
                if(a[ partsArray[0]])
                {
                    a[ partsArray[0]]+=last;
                }
                else 
                {
                    a[ partsArray[0]]=last;
                }
            }
            else
            {
                for(const e of  partsArray[0])
                {
                    const v = e as BodyPartConstant;
                    if(a[v ])
                    {
                        a[v]+=last;
                    }
                    else 
                    {
                        a[v]=last;
                    }
                }
            }
           
        } else {
           for(const e of partsArray)
           {
               const ret = this.flatBodyPartMap(e as BodyItem);
               for(let k in ret)
               {
                    const v = k as BodyPartConstant;
                    if(a[v])
                    {
                        a[v]+=ret[v];
                    }
                    else
                    {
                        a[v]=ret[v];
                    }
               }
           }
        }
        return a
    }
    /**
     * 计算孵化需要的能量
     * @param body
     */
    static calculateSpawnEnergy(body: BodyPartConstant[]): number {
        return body.reduce((sum, part) => sum + BODYPART_COST[part], 0)
    }
    /**
     * 找到部件分类对象中最多的部件，当move和另一部件均为最多时，选择另一部件
     * @param partsClassifyObj 部件分类对象
     */
    static findMostParts(partsClassifyObj: PartsClassify): BodyPartConstant | null {
        let mostPart: BodyPartConstant | null = null
        let mostPartNumber = 2
        for (const type in partsClassifyObj) {
            const number = partsClassifyObj[type as BodyPartConstant]
            if (!number) continue
            if (number > mostPartNumber || (number === mostPartNumber && type !== 'move')) {
                mostPart = type as BodyPartConstant
                mostPartNumber = number
            }
        }
        return mostPart
    }

    /**
     * 自适应体型
     * @param bodyParts 身体部件
     * @param maxEnergy 最大能量
     */
    static adaptiveBody(bodyParts: BodyPartConstant[], maxEnergy: number,moveboost:number=0,speed = 1): BodyPartConstant[] {
        const partsClassifyObj = this.classifyBodyParts(bodyParts)
        // 先统计其他部件和move部件比值
        let moveNumber = partsClassifyObj['move'] || 0
        let otherNumber = bodyParts.length - moveNumber
        // 处理没有move部件的爬，比如某些挖过道的爬
        let ratio = moveNumber ? Math.ceil(otherNumber / moveNumber) : 10000
        // 处理只有move部件的爬
        ratio = Math.max(1, ratio)
        while (this.calculateSpawnEnergy(bodyParts) > maxEnergy) {
            // 这里有个隐藏bug，某个部件可能会被减没，不过一般没事，摆烂了
            if (maxEnergy < 300) return bodyParts
            const mostPart = this.findMostParts(partsClassifyObj)
            if (!mostPart) {
                return bodyParts
            }
            if(moveboost == 0)
            {
                // 不进行boost则优先砍掉tough
                const index = bodyParts.indexOf("tough");
                if(index>=0)
                {
                    bodyParts.splice(index, 1);
                    continue;
                }
            }
            const index = bodyParts.indexOf(mostPart)
            if (~index) {
                bodyParts.splice(index, 1)
                const mostPartRecord = partsClassifyObj[mostPart]
                if (mostPartRecord) (partsClassifyObj[mostPart] as any)--
                else return bodyParts
                // 如果最多的部件已经是move部件了，不再重复进行下面move部件的删除
                if (mostPart === 'move') {
                    moveNumber--
                    continue
                }
                otherNumber--
            }
            // 同时删除move部件，至少留一个move部件
            if (moveNumber > 1 && otherNumber / (moveNumber - 1) <= ratio) {
                const move_index = bodyParts.indexOf('move')
                if (move_index > -1) {
                    bodyParts.splice(move_index, 1)
                    const movePartRecord = partsClassifyObj['move']
                    if (movePartRecord) (partsClassifyObj['move'] as any)--
                    moveNumber--
                }
            }
        }
        return bodyParts
    }
    /**
     * 自适应体型,根据能量最大值调整体型。并且保证满速度
     * @param bodyParts 身体部件
     * @param maxEnergy 最大能量
     */
    static adaptiveBodys(bodyParts: BodyPartConstant[], maxEnergy: number,moveboost:BoostLevel=0 ): BodyPartConstant[] {
        const partsClassifyObj = this.classifyBodyParts(bodyParts)
        // 先统计其他部件和move部件比值
        let moveNumber = partsClassifyObj['move'] || 0
        let otherNumber = bodyParts.length - moveNumber
        // 处理没有move部件的爬，比如某些挖过道的爬
        let ratio = moveNumber ? Math.ceil(otherNumber / moveNumber) : 10000
        // 处理只有move部件的爬
        ratio = Math.max(1, ratio)
        while (this.calculateSpawnEnergy(bodyParts) > maxEnergy) {
            // 这里有个隐藏bug，某个部件可能会被减没，不过一般没事，摆烂了
            if (maxEnergy < 300) return bodyParts
            const mostPart = this.findMostParts(partsClassifyObj)
            if (!mostPart) {
                return bodyParts
            }
            // tough 清理
            if(moveboost == 0)
            {
                // 不进行boost则优先砍掉tough
                const index = bodyParts.indexOf("tough");
                if(index>=0)
                {
                    bodyParts.splice(index, 1);
                    continue;
                }
            }
            // 负重 清理
            const needmove = this.getMoveCount(otherNumber,moveboost);
            if(moveNumber>needmove)
            {
                // move 有多余
                (partsClassifyObj["move"] as any)--;
            }
            else
            {
                // move 没有多余 其他body 数量 -1
                (partsClassifyObj[mostPart] as any)--;
                const needmove2 =  this.getMoveCount(otherNumber-1,moveboost);
                if(moveNumber>needmove2)
                {
                    (partsClassifyObj["move"] as any)--;
                }
            }
        }
        return bodyParts
    }
    /**
     * 
     * @param body body配置
     * @param bodytype 按照 bodytype来计算
     * @param num bodytype 的总数
     * @returns num 个bodytype  按照 body配置 应该可以出多少爬
     */
    public static getCreepCount(body:BodyItem[],bodytype:BodyPartConstant,num:number)
    {
        const bodyrecord = BodyUtils.flatBodyPartMap(body);
        if(!bodyrecord[bodytype])
        {
            throw new Error(`${bodytype}不存在body中 ${JSON.stringify(body)}`);
        }
        return  Math.round(num/bodyrecord[bodytype]);
    }

    public static getBodyCountCost(body: BodyPartConstant[],amount:number=1)
    {
        return  Math.ceil((body.length + 0.333)*amount)
    }

    /**
     * 低等级的body配置
     * 根据我方需要的攻击力，和治疗量。求得body的配置信息,这是红球换伤配置.红球在后面
     * @param attack 
     * @param heal 
     * @param energycap 
     * @param boostLevel 
     */
    public static calBodyConfig(attack:number,heal:number,energycap:number,boostLevel:number=0):[BodyItem,number]
    {
        const  attackNum= Math.ceil( attack*1.0/ BODYPART_ABILITY_Map["attack"]);
        const healNum = Math.ceil( heal*1.0/ BODYPART_ABILITY_Map["heal"]);

    
        const movenum = attackNum+healNum;

        const erergycost = this.calculateEnergyCost([[ATTACK,attackNum],[HEAL,healNum],[MOVE,movenum]]);

        const creepnum = Math.ceil( erergycost*1.0/energycap);
        if(creepnum>1)
        {
            [[[MOVE,Math.floor(movenum/creepnum)],[ATTACK,Math.floor(attackNum/creepnum)],[HEAL,Math.floor(healNum/creepnum)]],creepnum]
        }
        return [[[MOVE,movenum],[HEAL,healNum],[ATTACK,attackNum]],1]
    }

    /**
     * 根据body的总数配置。计算当前能量生产的creep数量
     * @param bodys 
     * @param energycap 
     * @returns 
     */
    public static calBodyPartsConfig(bodys:Record<BodyPartConstant, number>,energycap:number ):[BodyItem,number]
    {
        const bodyitem:[BodyPart, number][]=[];
        for(const k in bodys)
        {
            bodyitem.push([k as BodyPartConstant, bodys[k as BodyPartConstant]]);
        }
        const erergycost = this.calculateEnergyCost(bodyitem);

        // 能量限制
        let creepnum = Math.ceil( erergycost*1.0/energycap);
        // body 数量限制
        const creepnum2 =  Math.ceil(_.sum(bodyitem,e=>e[1])*1.0/50);
        creepnum = Math.max(creepnum,creepnum2);
        if(creepnum>1)
        {
            const rebuildBody:[BodyPart, number][]=[];
            for(const e of bodyitem)
            {
                rebuildBody.push([e[0],Math.floor(e[1]/creepnum)])
            }
            return [rebuildBody,creepnum]
        }
        return [bodyitem,1]
    }
    /**
     * 把body按照数量均分
     * @param bodys 
     * @param creepcount 
     * @returns 
     */
    private static averageBody(bodys:Record<BodyPartConstant, number>,creepcount:number)
    {
        const bodyitem:[BodyPart, number][]=[];
        for(const k in bodys)
        {
            if(bodys[k as BodyPartConstant]>0)
            {
                const leastcount =  Math.floor(bodys[k as BodyPartConstant]/creepcount);
                
                bodyitem.push([k as BodyPartConstant, leastcount==0?1:leastcount]);
            }
        }
        return bodyitem
    }

    /**
     * 根据bodypart的配置 以及需要分成creep的个数。配置各个creep的body配置信息
     * @param bodys 
     * @param creepcount 
     */
    public static calRoleConfig(bodys:Record<BodyPartConstant, number>,creepcount:number ):[IBodyConfig,number][]|false
    {
        const attackcount = bodys[ATTACK]?bodys[ATTACK]:0;
        const rangeattackcount = bodys[RANGED_ATTACK]?bodys[RANGED_ATTACK]:0;
        for(let cn=creepcount;cn<=4;cn++)
        {
            let ret ; 
            if(attackcount> rangeattackcount)
            {
                ret =  this.calRedRoleConfig(bodys,creepcount)
            }
            else
            {
                ret =  this.calBlueRoleConfig(bodys,creepcount)
            }
             
            if(ret.every(e=>
                {
                    const v= (e[0].body as any as [BodyPart,number][]);
                    return v.reduce((a,b)=>a+b[1],0) <=50
                 
                    // return true;
                }))
            {
                return ret;
            }
        }
        return false;
    }

    /**
     * attack为主
    */
    private static calRedRoleConfig(bodys:Record<BodyPartConstant, number>,creepcount:number):[IBodyConfig,number][]
    {
        if(creepcount<=1)
        {
            // 平均分配
            return [[{body:this.averageBody(bodys,creepcount),Role:"idaRed"},creepcount]];
        }
        else  if(creepcount<=2)
        {
            const creep =  this.redistrictBodyTo2Creep(bodys,ATTACK,0.7,2);
            return [
                [{body:creep.attack,Role:"idaRed"},1],
                [{body:creep.heal,Role:"idaHeal"},1],
            ]
        }
        // else  if(creepcount<=3)
        // {
        //     const creep =  this.redistrictBodyTo2Creep(bodys,ATTACK,1,3);
        //     // 小队加上一个一体机
        //     return [
        //         [{body:creep.attack,Role:"idaRed"},1],
        //         [{body:creep.heal,Role:"idaHeal"},1],
        //         [{body:this.averageBody(bodys,creepcount),Role:"idaRed"},creepcount]
        //     ]
        // }
        else
        {
            // 4人小队
            const creep =  this.redistrictBodyTo2Creep(bodys,ATTACK,1,4);
            return [
                [{body:creep.attack,Role:"idaRed"},2],
                [{body:creep.heal,Role:"idaHeal"},2],
            ]
        } 
    }
    /**
     * ra为主
     * @param bodys 
     * @param creepcount 最多4个
     */
    private static calBlueRoleConfig(bodys:Record<BodyPartConstant, number>,creepcount:number):[IBodyConfig,number][]
    {
        if(creepcount<=3)
        {
            // 平均分配
            return [[{body:this.averageBody(bodys,creepcount),Role:"idaBlue"},creepcount]];
        }
        else
        {
            // 4人小队
            const creep =  this.redistrictBodyTo2Creep(bodys,RANGED_ATTACK,0.7,4);
            return [
                [{body:creep.attack,Role:"idaBlue"},2],
                [{body:creep.heal,Role:"idaHeal"},2],
            ]
        }   
    }

    /**
     * 把body按照比例重新分配到两个爬里面,攻击和治疗
     * @param bodys 
     * @param keybody 
     * @param attackradio 0-1 attack 攻击body占总攻击body的占比
     * @param creepcount bodys被分为几个爬
     */
    private static redistrictBodyTo2Creep(bodys:Record<BodyPartConstant, number>,keybody:ATTACK|RANGED_ATTACK,attackradio:number,creepcount:number)
    {
        const allBodyCount = Object.values(bodys).reduce((a,b)=>a+b);
        const movecount = bodys[MOVE];
        const moveRadio = movecount*1.0/(allBodyCount-movecount);   // move与其他body的比例
        const r =(creepcount/2);
        let attackcount = bodys[keybody]/r;
        let healcount = bodys[HEAL]/r;

        let bodyAttackcountA = Math.round(attackcount*attackradio);
        let bodyAttackcountB = attackcount-bodyAttackcountA;
        let bodyHealcountB = Math.round(healcount*attackradio);
        let bodyHealcountA = healcount-bodyHealcountB;
        // 其他body平均分配
        let body_A:BodyItem=[],body_H:BodyItem=[];
        if(moveRadio ==1)
        {
            // tough可以丢弃
            for(let k in bodys)
            {
                if(k == keybody)
                {
                    body_A.push([k , bodyAttackcountA])
                    body_H.push([k ,bodyAttackcountB])
                }
                else if (k== HEAL)
                {
                    body_A.push([k , bodyHealcountA])
                    body_H.push([k ,bodyHealcountB])
                }
                else if (k!= MOVE && k!= TOUGH)
                {
                    body_A.push([k as BodyPartConstant,Math.floor(bodys[k as BodyPartConstant]/creepcount)]);
                    body_H.push([k as BodyPartConstant,Math.floor(bodys[k as BodyPartConstant]/creepcount)]);
                }
            }
            // 补充move 再判断是否补充tough
            const appendBody =(r:number,body:BodyItem[],toughcount:number)=>
            {
                const bcount = body.reduce((a,b)=>a+(b[1] as number),0)
               
                for(let i=toughcount;i>=0;i--)
                {
                    bcount+i;
                    const moveCount = Math.ceil(r*(bcount+i));
                    if(moveCount+bcount+i<=50)
                    {
                        body.push([TOUGH,i]);
                        body.push([MOVE,moveCount]);
                        return true
                    }
                }
                return false
            }
            appendBody(moveRadio,body_A,bodys[TOUGH]);
            appendBody(moveRadio,body_H,bodys[TOUGH]);
            return {attack:body_A,heal:body_H}
        }
        else 
        {
            for(let k in bodys)
            {
                if(k == keybody)
                {
                    body_A.push([k , bodyAttackcountA])
                    body_H.push([k ,bodyAttackcountB])
                }
                else if (k== HEAL)
                {
                    body_A.push([k , bodyHealcountA])
                    body_H.push([k ,bodyHealcountB])
                }
                else if (k!= MOVE && k!= TOUGH)
                {
                    body_A.push([k as BodyPartConstant,Math.floor(bodys[k as BodyPartConstant]/creepcount)]);
                    body_H.push([k as BodyPartConstant,Math.floor(bodys[k as BodyPartConstant]/creepcount)]);
                }
                else if (k== TOUGH)
                {
                    body_A.push([k as BodyPartConstant,Math.floor(bodys[k as BodyPartConstant]/creepcount)]);
                    body_H.push([k as BodyPartConstant,Math.floor(bodys[k as BodyPartConstant]/creepcount)]);
                }
            }
            return {attack:body_A,heal:body_H}
        }
       
    }

    /**
     * 重新对body进行排序
     * @param body 
     * @param toughfisrt 
     */
    static recombineBody(body:[BodyPartConstant,number][],toughfisrt:boolean=true)
    {
        // 1.最后面留一个move
        // 2.heal 放在move前面
        // 3.判断tough的位置
        const sum_body = _.sum(body,e=>e[1]);
        let newbody:[BodyPartConstant,number][]=[];
        {
            const index = body.findIndex(e=>e[0]== MOVE);
  
            if(index>=0 && body[index][1]>0)
            {
                newbody.push([MOVE,1]);
                body[index][1]--;
            }
        }
        {
            const  index = body.findIndex(e=>e[0]== HEAL);
            if(index>=0 && body[index][1]>0)
            {
                newbody.splice(0,0,body[index])
                body.splice(index,1);
            }
        }
        if(toughfisrt)
        {
       
            for(let index=0;index<body.length;)
            {
                if(body[index][0] != TOUGH)
                {
                    newbody.splice(0,0,body[index])
                    body.splice(index,1);
                }
                else
                {
                    index++;
                }
            }
            newbody = body.concat(newbody);
        }
        else
        {
            // tough不在最前面
            const frontBody: [BodyPartConstant,number][]=[];
            let toughindex = Math.ceil( sum_body/5);
            let sumindex=0;
            const toughtmpinde = body.findIndex(e=>e[0]== TOUGH);
            const toughbody  = body[toughtmpinde];
            if(toughbody)
            {
                body.splice(toughtmpinde,1);
            }
         
            for(let index=0;index<body.length;index++)
            {
                if((sumindex +body[index][1])> toughindex && sumindex<=toughindex)
                {
                    // 要切割
                    const splitcount = toughindex-sumindex;
                    const left= _.clone(body[index]);
                    const right= _.clone(body[index]);
                    left[1]=splitcount;
                    right[1]= (sumindex +body[index][1]) -toughindex;
                    frontBody.push(left);
                    if(toughbody)
                    {
                        frontBody.push(toughbody);
                    }
                    frontBody.push(right);
                }
                else
                {
                    frontBody.push(body[index]);
                    // body.splice(index,1);
                }
                sumindex+=body[index][1];
            }
            newbody = frontBody.concat(newbody)
        }
        return newbody
    }
}